﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Iot.Shared.Helper;
using Microsoft.Extensions.Logging;

namespace Communication.DataReceivedEvent.Can.Itekon;

public partial class Itekon(CanInfo info, ILogger logger) : IDataReceivedEvent
{
    private const string DllName = "controlcan.dll";

    /// <summary>
    ///     回调函数
    /// </summary>
    private Action<byte[]?>? _action;

    /// <summary>
    ///     第几路 CAN。
    /// </summary>
    private uint _canInd;

    /// <summary>
    ///     设备类型号
    /// </summary>
    private uint _device;

    /// <summary>
    ///     设备索引
    /// </summary>
    private uint _deviceInd;


    public ILogger Logger => logger;

    public bool Connected { get; private set; }

    public void Close()
    {
        var result = VCI_ResetCAN(_device, _deviceInd, _canInd);
        if (result == 1)
        {
            //复位成功
        }
        //复位失败
    }

    public void Dispose()
    {
        Close();
        GC.SuppressFinalize(this);
    }

    public bool Start(Action<byte[]?> action)
    {
        _action = action;
        if (Connected) return Connected;
        _device = (uint)info.Device;
        _deviceInd = (uint)info.DeviceInd;
        if (VCI_OpenDevice(_device, _deviceInd, 0) != 1) return false;
        var config = new VciInitConfig
        {
            AccCode = 0x00000000,
            AccMask = 0xFFFFFFFF,
            Timing0 = 0x01, //波特率 250k
            Timing1 = 0x1C, //波特率 250k
            Filter = 0x01, //滤波方式 单滤波   
            Mode = 0x00 //模式正常 
        };
        _canInd = (uint)info.CanInd;
        if (VCI_InitCAN(_device, _deviceInd, _canInd, ref config) != 1) return false;
        if (VCI_ResetCAN(_device, _deviceInd, _canInd) != 1) return false;
        if (VCI_StartCAN(_device, _deviceInd, _canInd) != 1) return false;
        Connected = true;
        ThreadHelper.RunTask(RunAsync, ThreadPriority.Normal, true, "Can总线数据接收线程启动失败", logger);
        return true;
    }


    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        return info.ToString(format, formatProvider);
    }

    /// <summary>
    ///     此函数用以打开设备
    /// </summary>
    /// <param name="deviceType">设备类型号。</param>
    /// <param name="deviceInd">设备索引号，当只有一个 USB CAN 时，索引号为 0，有两个时可以为 0 或 1。</param>
    /// <param name="reserved">此参数无意义。</param>
    /// <returns>为 1 表示操作成功，0 表示操作失败。</returns>
    [LibraryImport(DllName)]
    private static partial uint VCI_OpenDevice(uint deviceType, uint deviceInd, uint reserved);


    /// <summary>
    ///     此函数用以初始化指定的 CAN。
    /// </summary>
    /// <param name="deviceType">设备类型号。</param>
    /// <param name="deviceInd">设备索引号，比如当只有一个 USB CAN 时，索引号为 0，有两个时可以为 0或 1。</param>
    /// <param name="canInd">第几路 CAN。</param>
    /// <param name="pInitConfig">初始化参数结构</param>
    /// <returns>为 1 表示操作成功，0 表示操作失败。</returns>
    [LibraryImport(DllName)]
    private static partial uint VCI_InitCAN(uint deviceType, uint deviceInd, uint canInd,
        ref VciInitConfig pInitConfig);

    /// <summary>
    ///     此函数用以获取指定接收缓冲区中接收到但尚未被读取的帧数。
    /// </summary>
    /// <param name="deviceType">设备类型号。</param>
    /// <param name="deviceInd">设备索引号，当只有一个设备时，索引号为 0，有两个时可以为 0 或 1。</param>
    /// <param name="canInd">第几路 CAN。</param>
    /// <returns>返回尚未被读取的帧数。</returns>
    [LibraryImport(DllName)]
    private static partial uint VCI_GetReceiveNum(uint deviceType, uint deviceInd, uint canInd);


    /// <summary>
    /// </summary>
    /// <param name="deviceType">设备类型号。</param>
    /// <param name="deviceInd">设备索引号，只有一个设备时，索引号为 0，有两个时可以为 0 或 1。</param>
    /// <param name="canInd">第几路 CAN。</param>
    /// <param name="pReceive">用来接收的数据帧数组的首指针。</param>
    /// <param name="len">用来接收的数据帧数组的长度。</param>
    /// <param name="waitTime">等待超时时间，以毫秒为单位。</param>
    /// <returns>返回实际读取到的帧数。如果返回值为 0xFFFFFFFF，则表示读取数据失败，有错误发生，请调用 VCI_ReadErrInfo 函数来获取错误码。</returns>
    [LibraryImport(DllName)]
    private static partial uint VCI_Receive(uint deviceType, uint deviceInd, uint canInd, [Out] byte[] pReceive,
        uint len, uint waitTime);

    /// <summary>
    ///     此函数用以复位 CAN
    /// </summary>
    /// <param name="deviceType">设备类型号。</param>
    /// <param name="deviceInd">设备索引号，只有一个设备时，索引号为 0，有两个时可以为 0 或 1。</param>
    /// <param name="canInd">第几路 CAN。</param>
    /// <returns>为 1 表示操作成功，0 表示操作失败。（注：在 CANET 中无此函数）</returns>
    [LibraryImport(DllName)]
    private static partial uint VCI_ResetCAN(uint deviceType, uint deviceInd, uint canInd);


    /// <summary>
    ///     此函数用以启动 CAN。
    /// </summary>
    /// <param name="deviceType">设备类型号。</param>
    /// <param name="deviceInd">设备索引号，只有一个设备时，索引号为 0，有两个时可以为 0 或 1。</param>
    /// <param name="canInd">第几路 CAN。</param>
    /// <returns>为 1 表示操作成功，0 表示操作失败。</returns>
    [LibraryImport(DllName)]
    private static partial uint VCI_StartCAN(uint deviceType, uint deviceInd, uint canInd);

    private static VciCanObj[] ByteToVciCanObj(byte[] data)
    {
        var result = new VciCanObj[data.Length / Marshal.SizeOf<VciCanObj>()];
        using var memory = new MemoryStream(data);
        using var binary = new BinaryReader(memory);
        for (var i = 0; i < result.Length; i++)
        {
            result[i].Id = binary.ReadUInt32();
            result[i].TimeStamp = binary.ReadUInt32();
            result[i].TimeFlag = binary.ReadByte();
            result[i].SendType = binary.ReadByte();
            result[i].RemoteFlag = binary.ReadByte();
            result[i].ExternFlag = binary.ReadByte();
            result[i].DataLen = binary.ReadByte();
            result[i].Data = binary.ReadBytes(8);
            result[i].Reserved = binary.ReadBytes(3);
        }

        return result;
    }

    private async Task RunAsync()
    {
        try
        {
            using var timer = new PeriodicTimer(new TimeSpan(0, 0, 0, 0, 200)); //创建定时器
            while (await timer.WaitForNextTickAsync())
            {
                try
                {
                    var len = VCI_GetReceiveNum(_device, _deviceInd, _canInd);
                    if (len <= 0) continue;
                    len = len > 50 ? 50 : len;
                    var data = new byte[len * Marshal.SizeOf<VciCanObj>()];
                    if (VCI_Receive(_device, _deviceInd, _canInd, data, len, 100) <= 0) continue;
                    foreach (var items in ByteToVciCanObj(data))
                    {
                        _action?.Invoke(items.Data);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct VciCanObj
    {
        public uint Id;
        public uint TimeStamp;
        public byte TimeFlag;
        public byte SendType;
        public byte RemoteFlag; //是否是远程帧
        public byte ExternFlag; //是否是扩展帧
        public byte DataLen;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] Data;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] Reserved;
    }

    /// <summary>
    ///     VciInitConfig 结 构 体 定 义 了 初 始 化 CAN 的 配 置 。 结 构 体 将 在 VCI_InitCan 函数中被填充
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct VciInitConfig
    {
        /// <summary>
        ///     验收码。
        /// </summary>
        public uint AccCode;

        /// <summary>
        ///     屏蔽码。
        /// </summary>
        public uint AccMask;

        /// <summary>
        ///     保留。
        /// </summary>
        public uint Reserved;

        /// <summary>
        ///     滤波方式。
        /// </summary>
        public byte Filter;

        /// <summary>
        ///     定时器 0（BTR0）。
        /// </summary>
        public byte Timing0;

        /// <summary>
        ///     定时器 1（BTR1）。
        /// </summary>
        public byte Timing1;

        /// <summary>
        ///     模式。
        /// </summary>
        public byte Mode;
    }
}